//------------------------------------------------------------------------------
// <copyright file="SafeNativeMethods.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
// <owner current="true" primary="false">Microsoft</owner>
// <owner current="true" primary="false">Microsoft</owner>
//------------------------------------------------------------------------------

using System;
using System.Diagnostics;
using System.Text;
using System.Runtime.InteropServices;
using System.Security;
using Microsoft.Win32.SafeHandles;
using System.Data.Common;
using System.Runtime.Versioning;
using System.Diagnostics.CodeAnalysis;

namespace System.Data.SqlTypes 
{
    [SuppressUnmanagedCodeSecurity]
    internal static class UnsafeNativeMethods
    {
        #region PInvoke methods
        
        [DllImport("NtDll.dll", CharSet = CharSet.Unicode)]
        [ResourceExposure(ResourceScope.Machine)]
        internal static extern UInt32 NtCreateFile
            (
                out Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle,
                Int32 desiredAccess,
                ref OBJECT_ATTRIBUTES objectAttributes,
                out IO_STATUS_BLOCK ioStatusBlock,
                ref Int64 allocationSize,
                UInt32 fileAttributes,
                System.IO.FileShare shareAccess,
                UInt32 createDisposition,
                UInt32 createOptions,
                SafeHandle eaBuffer,
                UInt32 eaLength
            );

        [DllImport("Kernel32.dll", SetLastError = true)]
        [ResourceExposure(ResourceScope.None)]
        internal static extern FileType GetFileType
            (
                Microsoft.Win32.SafeHandles.SafeFileHandle hFile
            );

        // do not use this PInvoke directly, use SafeGetFullPathName instead
        [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        [ResourceExposure(ResourceScope.Machine)]
        private static extern int GetFullPathName
            (
                string path, 
                int numBufferChars,
                StringBuilder buffer,
                IntPtr lpFilePartOrNull
            );

        /// <summary>
        /// safe wrapper for GetFullPathName
        /// check that the path length is less than Int16.MaxValue before calling this API!
        /// </summary>
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        internal static string SafeGetFullPathName(string path)
        {
            Debug.Assert(path != null, "path is null?");
            // make sure to test for Int16.MaxValue limit before calling this method
            // see the below comment re GetLastWin32Error for the reason
            Debug.Assert(path.Length < Int16.MaxValue);

            // since we expect network paths, the 'full path' is expected to be the same size
            // as the provided one. we still need to allocate +1 for null termination
            StringBuilder buffer = new StringBuilder(path.Length + 1);

            int cchRequiredSize = GetFullPathName(path, buffer.Capacity, buffer, IntPtr.Zero);

            // if our buffer was smaller than required, GetFullPathName will succeed and return us the required buffer size with null
            if (cchRequiredSize > buffer.Capacity)
            {
                // we have to reallocate and retry
                buffer.Capacity = cchRequiredSize;
                cchRequiredSize = GetFullPathName(path, buffer.Capacity, buffer, IntPtr.Zero);
            }

            if (cchRequiredSize == 0)
            {
                // GetFullPathName call failed 
                int lastError = Marshal.GetLastWin32Error();
                if (lastError == 0)
                {
                    // we found that in some cases GetFullPathName fail but does not set the last error value
                    // for example, it happens when the path provided to it is longer than 32K: return value is 0 (failure)
                    // but GetLastError was zero too so we raised Win32Exception saying "The operation completed successfully".
                    // To raise proper "path too long" failure, check the length before calling this API.
                    // For other (yet unknown cases), we will throw InvalidPath message since we do not know what exactly happened
                    throw ADP.Argument(Res.GetString(Res.SqlFileStream_InvalidPath), "path");
                }
                else
                {
                    System.ComponentModel.Win32Exception e = new System.ComponentModel.Win32Exception(lastError);
                    ADP.TraceExceptionAsReturnValue(e);
                    throw e;
                }
            }

            // this should not happen since we already reallocate
            Debug.Assert(cchRequiredSize <= buffer.Capacity, string.Format(
                System.Globalization.CultureInfo.InvariantCulture,
                "second call to GetFullPathName returned greater size: {0} > {1}", 
                cchRequiredSize, 
                buffer.Capacity));

            return buffer.ToString();
        }

        // RTM versions of Win7 and Windows Server 2008 R2
        private static readonly Version ThreadErrorModeMinOsVersion = new Version(6, 1, 7600);

        // do not use this method directly, use SetErrorModeWrapper instead
        [DllImport("Kernel32.dll", ExactSpelling = true)]
        [ResourceExposure(ResourceScope.Process)]
        private static extern uint SetErrorMode(uint mode);

        // do not use this method directly, use SetErrorModeWrapper instead
        // this API exists since Windows 7 / Windows Server 2008 R2
        [DllImport("Kernel32.dll", ExactSpelling = true, SetLastError = true)]
        [ResourceExposure(ResourceScope.None)]
        [SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")]
        private static extern bool SetThreadErrorMode(uint newMode, out uint oldMode);

        /// <summary>
        /// this method uses thread-safe version of SetErrorMode on Windows 7/Windows Server 2008 R2 operating systems.
        /// </summary>
        [ResourceExposure(ResourceScope.Process)] // None on Windows7 / Windows Server 2008 R2 or later
        [ResourceConsumption(ResourceScope.Process)]
        internal static void SetErrorModeWrapper(uint mode, out uint oldMode)
        {
            if (Environment.OSVersion.Version >= ThreadErrorModeMinOsVersion)
            {
                // safe to use new API
                if (!SetThreadErrorMode(mode, out oldMode))
                {
                    throw new System.ComponentModel.Win32Exception();
                }
            }
            else
            {
                // cannot use the new SetThreadErrorMode API on current OS, fallback to the old one
                oldMode = SetErrorMode(mode);
            }
        }

        [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        [ResourceExposure(ResourceScope.Machine)]
        internal static extern bool DeviceIoControl
            (
                Microsoft.Win32.SafeHandles.SafeFileHandle fileHandle,
                uint ioControlCode,
                IntPtr inBuffer,
                uint cbInBuffer,
                IntPtr outBuffer,
                uint cbOutBuffer,
                out uint cbBytesReturned,
                IntPtr overlapped
            );

        [DllImport("NtDll.dll")]
        [ResourceExposure(ResourceScope.None)]
        internal static extern UInt32 RtlNtStatusToDosError
            (
                UInt32 status
            );

        #region definitions from devioctl.h

        internal const ushort FILE_DEVICE_FILE_SYSTEM = 0x0009;
        
        internal enum Method
        {
            METHOD_BUFFERED,
            METHOD_IN_DIRECT,
            METHOD_OUT_DIRECT,
            METHOD_NEITHER
        };

        internal enum Access
        {
            FILE_ANY_ACCESS,
            FILE_READ_ACCESS,
            FILE_WRITE_ACCESS
        }

        internal static uint CTL_CODE
            (
                ushort deviceType,
                ushort function,
                byte method,
                byte access
            )
        {
            if ( function > 4095 )
                throw ADP.ArgumentOutOfRange ( "function" );

            return (uint) ( ( deviceType << 16 ) | ( access << 14 ) | ( function << 2 ) | method );
        }

        #endregion
        
        #endregion
        
        #region Error codes
        
        internal const int ERROR_INVALID_HANDLE             = 6;
        internal const int ERROR_MR_MID_NOT_FOUND           = 317;
        
        internal const uint STATUS_INVALID_PARAMETER        = 0xc000000d;
        internal const uint STATUS_SHARING_VIOLATION        = 0xc0000043;
        internal const uint STATUS_OBJECT_NAME_NOT_FOUND    = 0xc0000034;

        #endregion
        
        internal const uint SEM_FAILCRITICALERRORS = 0x0001;
        
        internal enum FileType : uint
        {
            Unknown = 0x0000,   // FILE_TYPE_UNKNOWN
            Disk    = 0x0001,   // FILE_TYPE_DISK
            Char    = 0x0002,   // FILE_TYPE_CHAR
            Pipe    = 0x0003,   // FILE_TYPE_PIPE
            Remote  = 0x8000    // FILE_TYPE_REMOTE
        }

        #region definitions from wdm.h

        [StructLayoutAttribute(LayoutKind.Sequential)]
        internal struct OBJECT_ATTRIBUTES
        {
            internal int length;
            internal IntPtr rootDirectory;
            internal SafeHandle objectName;
            internal int attributes;
            internal IntPtr securityDescriptor;
            internal SafeHandle securityQualityOfService;
        }

        [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        internal struct UNICODE_STRING
        {
            internal UInt16 length;
            internal UInt16 maximumLength;
            internal string buffer;
        }

        // VSTFDevDiv # 547461 [Backport SqlFileStream fix on Win7 to QFE branch]
        // Win7 enforces correct values for the _SECURITY_QUALITY_OF_SERVICE.qos member.
        // taken from _SECURITY_IMPERSONATION_LEVEL enum definition in winnt.h
        internal enum SecurityImpersonationLevel
        {
            SecurityAnonymous,
            SecurityIdentification,
            SecurityImpersonation,
            SecurityDelegation
        }

        [StructLayoutAttribute(LayoutKind.Sequential)]
        internal struct SECURITY_QUALITY_OF_SERVICE
        {
            internal UInt32 length;
            [MarshalAs(UnmanagedType.I4)]
            internal int impersonationLevel;
            internal byte contextDynamicTrackingMode;
            internal byte effectiveOnly;
        }

        [StructLayoutAttribute(LayoutKind.Sequential)]
        internal struct IO_STATUS_BLOCK
        {
            internal UInt32 status;
            internal IntPtr information;
        }

        [StructLayoutAttribute(LayoutKind.Sequential)]
        internal struct FILE_FULL_EA_INFORMATION
        {
            internal UInt32 nextEntryOffset;
            internal Byte flags;
            internal Byte EaNameLength;
            internal UInt16 EaValueLength;
            internal Byte EaName;
        }

        [Flags]
        internal enum CreateOption : uint
        {
            FILE_WRITE_THROUGH = 0x00000002,
            FILE_SEQUENTIAL_ONLY = 0x00000004,
            FILE_NO_INTERMEDIATE_BUFFERING = 0x00000008,
            FILE_SYNCHRONOUS_IO_NONALERT = 0x00000020,
            FILE_RANDOM_ACCESS = 0x00000800
        }

        internal enum CreationDisposition : uint
        {
            FILE_SUPERSEDE = 0,
            FILE_OPEN = 1,
            FILE_CREATE = 2,
            FILE_OPEN_IF = 3,
            FILE_OVERWRITE = 4,
            FILE_OVERWRITE_IF = 5
        }
        
        #endregion

        #region definitions from winnt.h

        internal const int FILE_READ_DATA       = 0x0001;
        internal const int FILE_WRITE_DATA      = 0x0002;
        internal const int FILE_READ_ATTRIBUTES = 0x0080;
        internal const int SYNCHRONIZE          = 0x00100000;

        #endregion

        #region definitions from ntdef.h

        [Flags]
        internal enum Attributes : uint
        {
            Inherit             = 0x00000002,
            Permanent           = 0x00000010,
            Exclusive           = 0x00000020,
            CaseInsensitive     = 0x00000040,
            OpenIf              = 0x00000080,
            OpenLink            = 0x00000100,
            KernelHandle        = 0x00000200,
            ForceAccessCheck    = 0x00000400,
            ValidAttributes     = 0x000007F2
        }

        #endregion

    }
}
